/**
 * @file app\tasks\mobus\mobus.c
 *
 * Copyright (C) 2023
 *
 * mobus.c is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author HinsShum hinsshum@qq.com
 *
 * @encoding utf-8
 */

/*---------- includes ----------*/
#include "inc/mobus.h"
#include "errorno.h"
#include "inc/mobus_ops.h"
#include "options.h"
#include "resources.h"
#include "serial.h"
#include "systicks.h"
#include "tasks.h"
#include "timer.h"


/*---------- macro ----------*/
#define TAG "Mobus"

/*---------- type define ----------*/
typedef bool (*mobus_evt_cb_t)(protocol_mobus_evt_t evt, mobus_ack_result_t* result);
/*---------- variable prototype ----------*/
extern struct mobus_device mobus_device;
/*---------- function prototype ----------*/
static bool _broadcast_evt_cb(protocol_mobus_evt_t evt, mobus_ack_result_t* result);
static bool _ack_evt_cb(protocol_mobus_evt_t evt, mobus_ack_result_t* result);
static bool _unicast_evt_cb(protocol_mobus_evt_t evt, mobus_ack_result_t* result);
static bool _group_evt_cb(protocol_mobus_evt_t evt, mobus_ack_result_t* result);
static bool _multicast_evt_cb(protocol_mobus_evt_t evt, mobus_ack_result_t* result);
/*---------- variable ----------*/
static serial_transport_t mobus;
static struct protocol_callback evt_cbs[] = {
    {PROTOCOL_MOBUS_EVT_TYPE_UNICAST, _unicast_evt_cb},
    {PROTOCOL_MOBUS_EVT_TYPE_MULTICAST, _multicast_evt_cb},
    {PROTOCOL_MOBUS_EVT_TYPE_BROADCAST, _broadcast_evt_cb},
    {PROTOCOL_MOBUS_EVT_TYPE_ACK, _ack_evt_cb},
    {PROTOCOL_MOBUS_EVT_TYPE_GROUP, _group_evt_cb} };
/*---------- function ----------*/
static void _parse(const uint8_t* recv_buf, uint32_t recv_length,
    const uint8_t* trans_buf, uint32_t trans_length) {
/* mobus frame */
    protocol_mobus_parse(recv_buf, recv_length, trans_buf, trans_length);
}

static void _polling_in_systicks(void) {
    serial_transport_called_per_tick(mobus);
}

static int32_t _serial_irq_callback(uint32_t irq, void* args, uint32_t length) {
    uint8_t* byte = (uint8_t*)args;

    assert(args);
    for (uint32_t i = 0; i < length; ++i) {
        serial_transport_recv_byte(mobus, byte[i]);
    }

    return CY_EOK;
}

static int32_t _t35_irq_callback(uint32_t irq, void* args, uint32_t length) {
    serial_transport_timer_expired(mobus);

    return CY_EOK;
}
/***************    _protocol layer  begin   ***************/

static void _mobus_send(const uint8_t* pbuf, uint32_t length) {
    serial_transport_set_transmitter(mobus, pbuf, length);
}

static void _mobus_push(const uint8_t* pbuf, uint32_t length,
    uint32_t retrans) {
    serial_transport_set_transmitter_cache(mobus, pbuf, length, retrans & 0xFFFF,
        0);
}

static void _mobus_stop_retrans(void) {
    serial_transport_clear_transmitter(mobus);
}
static inline mobus_device_t __get_mobus_device(uint8_t native_address) {
    mobus_device_t dev = &mobus_device;

    return (dev->native_address == native_address ? dev : NULL);
}
static bool _unicast_evt_cb(protocol_mobus_evt_t evt,
    mobus_ack_result_t* result) {
    bool err = false;
    mobus_device_t dev = __get_mobus_device(evt->unicast.native_addr);
    uint8_t sub_channel = evt->unicast.sub_channel;
    if (evt->unicast.native_addr == 0xFF && evt->unicast.data[0] == 0xAA &&
        evt->unicast.data[1] == 0xBB && evt->unicast.data[2] == 0xBB) {
        dev = &mobus_device;
    }
    do {
        if (!dev) {
            break;
        }
        if ((((uint8_t)evt->unicast.number_of_channels << 4) |
            evt->unicast.sub_channel) == 0x88 &&
            evt->frame_type == MOBUS_FRAME_TYPE_CONFIG_DEVICE) {
            err = dev->evt_cb(dev, evt, result);
            break;
        }
        if (evt->unicast.number_of_channels == 0xF &&
            evt->unicast.sub_channel == 0xF) {
            err = dev->evt_cb(dev, evt, result);
            break;
        }
        if (evt->unicast.number_of_channels == 0x0 &&
            evt->unicast.cmd == 0x7) {
            err = dev->evt_cb(dev, evt, result);
            break;
        }
        if (evt->unicast.number_of_channels == 0x0 &&
            evt->unicast.cmd == 0x8) {
            err = dev->evt_cb(dev, evt, result);
            break;
        }
        if (evt->unicast.number_of_channels == 0x0 &&
            evt->unicast.cmd == 0x9) {
            err = dev->evt_cb(dev, evt, result);
            break;
        }
        err = dev->evt_cb(dev, evt, result);
    } while (0);

    return err;
}

static bool _broadcast_evt_cb(protocol_mobus_evt_t evt,
    mobus_ack_result_t* result) {
    mobus_device_t dev = &mobus_device;
    assert(dev);
    if (dev->evt_cb) {
        dev->evt_cb(dev, evt, result);
    }
    return false;
}

static bool _ack_evt_cb(protocol_mobus_evt_t evt, mobus_ack_result_t* result) {
    mobus_device_t dev = __get_mobus_device(evt->ack.native_addr);

    if (dev && dev->evt_cb) {
        dev->evt_cb(dev, evt, result);
    }
    return false;
}

static bool _group_evt_cb(protocol_mobus_evt_t evt,
    mobus_ack_result_t* result) {
    mobus_device_t dev = &mobus_device;
    assert(dev);
    if (dev && dev->evt_cb) {
        dev->evt_cb(dev, evt, result);
    }
    return false;
}
static bool _multicast_evt_cb(protocol_mobus_evt_t evt,
    mobus_ack_result_t* result) {
    mobus_device_t dev = &mobus_device;
    assert(dev);
    if (dev && dev->evt_cb) {
        dev->evt_cb(dev, evt, result);
    }
    return false;
}
static bool _mobus_on_event(protocol_mobus_evt_t evt,
    mobus_ack_result_t* result) {
    mobus_evt_cb_t cb = NULL;
    bool err = false;

    cb = protocol_callback_find(evt->evt_type, evt_cbs, ARRAY_SIZE(evt_cbs));
    assert(cb);
    if (cb) {
        err = cb(evt, result);
    }

    return err;
}

static inline void _protocol_mobus_init(void) {
    struct protocol_mobus_ops ops = {
        .send = _mobus_send,
        .push = _mobus_push,
        .stop_retrans = _mobus_stop_retrans,
        .evt_cb = _mobus_on_event,
    };

    protocol_mobus_init(&ops);
}
static inline void _mobus_device_init(mobus_device_t dev) {
    dev->init(dev, mobus);
    dev->req_access_to_mobus_network(dev);
}

static void _init(void) {
    struct serial_transport_ops ops = { 0 };
    void* serial = resources_get("dev_mobuscom");
    void* timer = resources_get("dev_mobust35");

    assert(serial);
    assert(timer);
    /* initialize protocol */
    _protocol_mobus_init();
    /* initialize serial transport level */
    mobus_ops_get(&ops.mac_ops);
    ops.mac_ops.receive_packet_parse = _parse;

    mobus = serial_transport_new(SERIAL_MAC_TYPE_HALFDUPLEX, 19200, 256, 256, 100, &ops);
    assert(mobus);
    /* set serial and timer interrupt callback */
    device_ioctl(serial, IOCTL_SERIAL_SET_IRQ_HANDLER, (void*)_serial_irq_callback); //
    device_ioctl(timer, IOCTL_TIMER_SET_IRQ_HANDLER, (void*)_t35_irq_callback);
    /* initalize mobus device */
    _mobus_device_init(&mobus_device);
    /* set systicks callback */
    systicks_add(_polling_in_systicks);
}

static void _polling(void) { serial_transport_poll(mobus); }

void mobus_init(void) {
    _init();
    daemon_add(_polling);
    /* say hi */
    xlog_tag_message(TAG, "Initialize successfully\n");
}
